iT邦幫忙

2024 iThome 鐵人賽

DAY 18
0
生成式 AI

T 大使 AI 之旅系列 第 18

【Day 18】Embeddings 的家 - 向量資料庫

  • 分享至 

  • xImage
  •  

前情提要

上一篇文章實作了簡單的 Retriever,那如果資料量越來越越多,或者是我的檔案是一個 PDF 檔的話,解析完的內容一多不可能存放在記憶體,所以有了今天的向量資料庫主題。
https://ithelp.ithome.com.tw/upload/images/20240823/20168336AhuNjPB3Xu.png

向量資料庫介紹 - Vector DataBase

一般的資料庫都沒辦法存放向量資料,像是 MySQL 之類的,所以有向量資料庫的誕生。那麼向量資料庫有很多種,目前就我所知最比較主流的幾個分別是 AstraDB、Chroma、Milvus、MongoDB、Pinecone、Qdrant、Redis。但就我所知有些是需要付費的,像是 Pinecone 和 Milvus 好像都是,那麼今天的實作會以 Qdrant 為主,一樣是基於 LangChain 架構。
LangChain 支援的向量資料庫這邊 -> LangChain VectorStores

Qdrant 向量資料庫

Qdrant 就跟市面上大多數資料庫一樣,有新增修改刪除查詢的基本功能,然後還有 filter 的篩選資料的功能 (支援聯集、交集)。Qdrant 的 collections 相當於一般資料庫的 table,payload 的功能相當於資料欄位,還有 metadata,然後 id 是 Primary Key 的概念,最後當然是可以儲存向量資料啦,可以儲存多筆的向量資料,圖片和文字的都可以。簡單分享一下選擇 Qdrant 的原因:

  1. 免費 (這真的超重要)
  2. 有 UI 介面
  3. 有 Retriever 功能

Qdrant 官網:Qdrant

本地安裝 Qdrant

Qdrant 有提供 Qdrant Cloud,但是只有 4GB。所以我們以本地安裝為主,可以透過 Docker 安裝在本機,安裝成功的話在 http://localhost:6333/dashboard 即可看到 Dashboard 介面。

# 從 Docker Hub 抓下 qdrant
docker pull qdrant/qdrant

# 啟動 qdrant
docker run -d --name qdrant -p 6333:6333 qdrant/qdrant

資料匯入向量資料庫實戰🔥

LangChain 框架的部分主要 Focus 在 Retriever 的部分,所以新增修改刪除查詢使用 Qdrant 自己的 qdrant-client 來編輯資料。

qdrant-client

from langchain_ffm import FFMEmbedding
from qdrant_client import QdrantClient
from qdrant_client.http import models
from qdrant_client.http.models import VectorParams, Distance
embeddings = FFMEmbedding()

# 連線到資料庫
client = QdrantClient(url="http://localhost:6333")

# 如果 collections 不存在可以就建立一個 collections
if client.collection_exists(collection_name="test"):
	pass
else:
	client.create_collection(
		collection_name="test",
		# The TWCC embedding model has 1536 dimensions
		vectors_config={
			"text": VectorParams(
				size=1536,
				distance=Distance.COSINE,
			),
		},
	)

# 欲匯入資料庫的資料
documents = [
	"Python 是一種高階編程語言,近年來被拿來做許多機器學習和深度學習的開發與模型訓練。",
	"Java 是一種面向對象的編程語言,廣泛應用於企業級應用和 Android 開發。",
	"JavaScript 是網頁開發的核心語言,允許動態操作 DOM 以實現交互效果。",
	"Go 是由 Google 開發的開源編程語言,因其高併發性能在雲端運算中大放異彩。",
	"C# 是由微軟開發的語言,主要用於 Windows 應用開發及遊戲開發,特別是在 Unity 引擎中。",
	"PHP 是一種伺服器端腳本語言,廣泛應用於網頁開發,尤其是動態內容生成。",
]

# 將資料全部匯入資料庫
for count, doc in enumerate(documents):
	client.upsert(
		collection_name="test",
		points=[
			models.PointStruct(
				id=count+1,
				vector={"text": embeddings.embed_query(doc)},
				payload={"text": doc},
			)
		],
	)

https://ithelp.ithome.com.tw/upload/images/20240822/20168336XhESbvp7ME.png
程式碼結果探討 🧐:

  • 這樣就可以看到上傳的結果,那根據自己想要的欄位新增在 payload 中,譬如說這筆資料是什麼分類的、關鍵字等等。
  • 藉由判斷 collections 是否存在來建立 collections
  • size 是文字 embeddings 之後的維度,譬如說台智雲是 1536、OpenAI 的 text-embedding-3-large 是 3072 等等
  • distance 是進行 Retriever 時的比對方式,大多使用 Cosine Similarity
  • upsert 即將資料匯入資料庫,id 就是 Primary Key 的概念,是不能重複的。vector 要與前面設定的名稱一致,維度也要一致。payload 就是可以加入自己想加的資料欄位。

langchain-qdrant

from langchain_qdrant import QdrantVectorStore
from langchain_ffm import FFMEmbedding
from qdrant_client import QdrantClient
from qdrant_client.http import models
from qdrant_client.http.models import VectorParams, Distance
from langchain_core.documents import Document
from uuid import uuid4
embeddings = FFMEmbedding()

# 欲匯入資料庫的資料
documents = [
	Document(page_content="Python 是一種高階編程語言,近年來被拿來做許多機器學習和深度學習的開發與模型訓練。", metadata={"code":"Python"}),
	Document(page_content="Java 是一種面向對象的編程語言,廣泛應用於企業級應用和 Android 開發。", metadata={"code":"Java"}),
	Document(page_content="JavaScript 是網頁開發的核心語言,允許動態操作 DOM 以實現交互效果。", metadata={"code":"js"}),
	Document(page_content="Go 是由 Google 開發的開源編程語言,因其高併發性能在雲端運算中大放異彩。", metadata={"code":"Go"}),
	Document(page_content="C# 是由微軟開發的語言,主要用於 Windows 應用開發及遊戲開發,特別是在 Unity 引擎中。", metadata={"code":"C#"}),
	Document(page_content="PHP 是一種伺服器端腳本語言,廣泛應用於網頁開發,尤其是動態內容生成。", metadata={"code":"PHP"}),
]

## 方法一
# 連線到資料庫
client = QdrantClient(url="http://localhost:6333")

# 如果 collections 不存在可以就建立一個 collections
if client.collection_exists(collection_name="ithome2024_method_1"):
	pass
else:
	client.create_collection(
		collection_name="ithome2024_method_1",
		# The TWCC embedding model has 1536 dimensions
		vectors_config={
			"text": VectorParams(
				size=1536,
				distance=Distance.COSINE,
			),
		},
	)

# 建立 vector stores
vector_store = QdrantVectorStore(
	client=client,
	collection_name="ithome2024_method_1",
	embedding=FFMEmbedding(),
	vector_name="text",
)

# 將資料轉向量匯入資料庫,Primary Key 給 uuid
ids = [str(uuid4()) for _ in range(len(documents))]
vector_store.add_documents(documents=documents, ids=ids)


## 方法二
qdrant = QdrantVectorStore.from_documents(
	documents,
	embeddings,
	url="localhost",
	collection_name="ithome2024_method_2",
)

https://ithelp.ithome.com.tw/upload/images/20240822/20168336ww9kW77cu1.png
程式碼結果探討 🧐:

  • 可以看到使用 LangChain 會程式碼比較簡短和方便,但是這個方式就是設定 metadata,而不是 payload,但本質上意思是一樣的,都是存放需要的其餘資料。
  • 這邊提供兩種方式,一種是還是需要透過 qdrant-client 去連線資料庫,而第二種方式指定本地端就可以自動上傳指定的地方,是最精簡的方式。

向量資料庫 Retriever 實戰🔥

from langchain_ffm import FFMEmbedding
from langchain_qdrant import QdrantVectorStore
embeddings = FFMEmbedding()

# 可以直接連線建立好的 collections
vector_store = QdrantVectorStore.from_existing_collection(
	url="http://localhost:6333",
	collection_name="ithome2024_method_1",
	embedding=embeddings,
	vector_name="text",
)

# Cosine Similarity 的 Retriever
cos_retriever = vector_store.as_retriever(search_type='similarity', search_kwargs={'k': 2})
print("這邊是 Cosine Similarity:")
result = cos_retriever.invoke('我想成為前端工程師!')
print(result[0].page_content, result[1].page_content)
result = vector_store.similarity_search(query='我想成為前端工程師!', k=2)
print(result[0].page_content, result[1].page_content)

# MMR 的 Retriever
mmr_retriever = vector_store.as_retriever(search_type='mmr', search_kwargs={'k': 2, 'lambda_mult': 0.25})
print("\n這邊是 MMR:")
result = mmr_retriever.invoke('我想成為前端工程師!')
print(result[0].page_content, result[1].page_content)
result = vector_store.max_marginal_relevance_search(query='我想成為前端工程師!', k=2, lambda_mult=0.25)
print(result[0].page_content, result[1].page_content)

https://ithelp.ithome.com.tw/upload/images/20240823/20168336YVeq0loeXX.png
程式碼結果探討 🧐:

  • 前面步驟已經先建立好資料庫,所以可以使用直接選取已經建立好的 collections。
  • 不管是在 Cosine Similarity 還是 MMR,其實只要使用 LangChain 寫法都很雷同,參數名稱也都一樣,如果今天換了一個 VectorDB 的話,參數名稱也會都一樣,這就是 LangChain 框架!

一站式向量資料庫實戰🔥

若是今天想要匯入資料庫之後針對其進行索引的話,那麼 LangChain 可以完成這件事情。不需要先匯入,然後找 collections 這麼麻煩,接下來來實戰!

from langchain_ffm import FFMEmbedding
from langchain_qdrant import QdrantVectorStore
from langchain_core.documents import Document
embeddings = FFMEmbedding()

# 欲匯入資料庫資料
documents = [
	Document(page_content="Python 是一種高階編程語言,近年來被拿來做許多機器學習和深度學習的開發與模型訓練。", metadata={"code":"Python"}),
	Document(page_content="Java 是一種面向對象的編程語言,廣泛應用於企業級應用和 Android 開發。", metadata={"code":"Java"}),
	Document(page_content="JavaScript 是網頁開發的核心語言,允許動態操作 DOM 以實現交互效果。", metadata={"code":"js"}),
	Document(page_content="Go 是由 Google 開發的開源編程語言,因其高併發性能在雲端運算中大放異彩。", metadata={"code":"Go"}),
	Document(page_content="C# 是由微軟開發的語言,主要用於 Windows 應用開發及遊戲開發,特別是在 Unity 引擎中。", metadata={"code":"C#"}),
	Document(page_content="PHP 是一種伺服器端腳本語言,廣泛應用於網頁開發,尤其是動態內容生成。", metadata={"code":"PHP"}),
]

# 匯入 Qdrant
qdrant = QdrantVectorStore.from_documents(
	documents,
	embeddings,
	url="http://localhost:6333",
	collection_name="ithome2024",
	force_recreate=True
)

# Retriever 結果
for result in qdrant.similarity_search(query='我想成為前端工程師!'):
	print(result.page_content)

https://ithelp.ithome.com.tw/upload/images/20240823/20168336PfJyNB1WhF.png
程式碼結果探討 🧐:

  • 使用這種方式,程式碼又更加的精簡。這個用途是如果有前端上傳一個檔案,可以透過解析完檔案內容,然後匯入 Qdrant,接著進行檢索,最後回傳使用者對應的答案。
  • 若沒設置 force_recreate,資料自動向後增加。

結論

今天簡單實作了 Qdrant 資料庫,雖然我除了 Qdrant 還沒有用過別的,但我覺得在 LangChain 架構下,每個資料庫被整合進來語法應該都不會差太多。我自己未來會想實作 MongoDB 和 Pinecone 之類的向量資料庫,有時間再來試試看吧!

https://ithelp.ithome.com.tw/upload/images/20240823/20168336JcpOoYoGYm.jpg
如果大家要使用 Qdrant 的話,記得看看 langchain-qdrant 支援的版本有沒有包含你本機的版本,我因為這個 bug 一直 error,但他根本沒顯示是版本問題。

題外話🤣

今天一整天開了一堆的會議,上班八小時有七小時在開會,在迷迷糊糊中度過了今天的上班時光。

下一篇文章:RAG 狂想曲 - AI 不再迷路


上一篇
【Day 17】Retriever 資料檢索 - 從資料海洋中釣出珍珠
下一篇
【Day 19】RAG 狂想曲 - AI 不再迷路
系列文
T 大使 AI 之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言